package org.statewalker.core;

import java.util.ArrayList;
import java.util.List;

import org.statewalker.core.IState.ICallback;

/**
 * Condition callback is used to call internal callback only when some specific
 * conditions are fulfilled. In the simplest case it could be a barrier waiting
 * when all forked processes are finished.
 * <p>
 * In the example above a state forks 10 new processes and waits until all of
 * them are finished.
 * </p>
 * 
 * <pre>
 * Example: 
 * 
 * public class MyState extends SimpleState {
 *      public MyState(
 *          IProcessContext context,
 *          IState parent,
 *          String stateKey) {
 *          super(context, parent, stateKey);
 *      }
 *      // ... 
 *      private Core fCore;
 *      public void activate(IEvent event, ICallback callback) {
 *      
 *          // The number of processes to spawn.
 *          int processNumber = 10;
 *          
 *          // Create a new barrier event which is used to wait until 
 *          // all forked processes call the #finish(IEvent) method on
 *          // the "processCallback" object.
 *          BarrierEvent barrier = new BarrierEvent("ok", processNumber);
 *          
 *          // This callback is used to collect calls from all forked 
 *          // processes. This object invokes the callback object 
 *          // when all processes are finished.
 *          ConditionalCallback processCallback = new ConditionalCallback(
 *              callback,
 *              barrier);
 *              
 *          // Spawn new processes. 
 *          for (int i = 0; i < processNumber; i++) {
 *              IProcessContext context = new MyProcessContext(i);
 *              fCore.startProcess("test", context, processCallback);
 *          }
 *      }
 *  }
 * </pre>
 * 
 * @author kotelnikov
 */
public class ConditionalCallback implements ICallback {

    /**
     * This barrier event is used to control the number of
     * {@link ConditionalCallback#finish(IEvent)} method calls. The
     * {@link #checkEvent(IEvent)} method returns <code>true</code> only when
     * the number of calls to this method is equals to the expected number. This
     * object could be used to wait for all forked processes.
     * 
     * @author kotelnikov
     */
    public static class BarrierEvent extends SimpleEvent
        implements
        IConditionEvent {

        /**
         * The list of events returned by forked processes.
         */
        private List<IEvent> fEvents = new ArrayList<IEvent>();

        /**
         * The expected number of events in the list (ie the number of forked
         * processes).
         */
        private final int fExpectedSize;

        /**
         * The default constructor. It sets the internal key of this event and
         * the expected number of events in the list.
         * 
         * @param key the key of this event
         * @param size the expected number of events in the list
         */
        public BarrierEvent(String key, int size) {
            super(key);
            fExpectedSize = size;
        }

        /**
         * This method returns <code>true</code> when the number of events in
         * the internal list equals to the expected number.
         * 
         * @see org.statewalker.core.ConditionalCallback.IConditionEvent#checkEvent(org.statewalker.core.IEvent)
         */
        public synchronized boolean checkEvent(IEvent event) {
            fEvents.add(event);
            return fEvents.size() >= fExpectedSize;
        }

        /**
         * Returns the full list of all events collected by this event
         * 
         * @return the full list of all events collected by this event
         */
        public List<IEvent> getEvents() {
            return fEvents;
        }

    }

    /**
     * Events of this type are used by {@link ConditionalCallback} instances to
     * control the calls to the underlying callback objects.
     * 
     * @author kotelnikov
     */
    public interface IConditionEvent extends IEvent {
        /**
         * This method could check some conditions and if they are met then it
         * returns <code>true</code>. This method is used to control the calls
         * to the {@link ICallback#finish(IEvent)} methods in the internal
         * {@link ICallback} object managed by the {@link ConditionalCallback}.
         * 
         * @param event the event to check
         * @return <code>true</code> if the callback object controlled by the
         *         {@link ConditionalCallback} instance should be called
         */
        boolean checkEvent(IEvent event);
    }

    /**
     * The internal callback object. This class calls the
     * {@link ICallback#finish(IEvent)} on this instance when the
     * {@link IConditionEvent#checkEvent(IEvent)} method returns
     * <code>true</code>.
     * 
     * @see #fCallbackEvent
     */
    private ICallback fCallback;

    /**
     * This event is used to call the {@link #fCallback} instance when the
     * {@link IConditionEvent#checkEvent(IEvent)} method of this event returns
     * <code>true</code>.
     * 
     * @see #fCallback
     */
    private IConditionEvent fCallbackEvent;

    /**
     * The default constructor used to initialize the internal fields
     * @param callbackEvent the event used to control and call the callback
     * @param callback the callback object; the method
     *        {@link ICallback#finish(IEvent)} on this instance is called when
     *        the {@link IConditionEvent#checkEvent(IEvent)} method returns
     *        <code>true</code>
     */
    public ConditionalCallback(IConditionEvent callbackEvent, ICallback callback) {
        fCallback = callback;
        fCallbackEvent = callbackEvent;
    }

    /**
     * This method calls the {@link IConditionEvent#checkEvent(IEvent)} method
     * and if it returns <code>true</code> then it calls the
     * {@link ICallback#finish(IEvent)} method on the internal callback
     * instance.
     * 
     * @see org.statewalker.core.IState.ICallback#finish(org.statewalker.core.IEvent)
     */
    public void finish(IEvent e) {
        if (fCallbackEvent.checkEvent(e)) {
            fCallback.finish(fCallbackEvent);
        }
    }

}